#ifndef __SOCKET_HPP__
#define __SOCKET_HPP__

#include "Logger.hpp"
#include "InetAddr.hpp"

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdlib.h>
#include <memory>

enum
{
    OK,
    SOCKET_CREATE_ERR,
    SOCKET_BIND_ERR

};

static const int gbacklog = 16;
static const int gsockfd = -1;

// 此处采用多态的方式，让基类提供方法，子类继承基类去实现；创建基类指针指向子类；
class Socket
{
public:
    virtual ~Socket() {};
    virtual void CreateSocketOrDie() = 0;
    virtual void BindSocketOrDie(int port) = 0;      // 让套接字与port IP进行绑定
    virtual void ListenSocketOrDie(int backlog) = 0; // 监听，需要传第二个参数
    virtual std::shared_ptr<Socket> Accept(InetAddr *clientaddr) = 0;
    virtual int Sockfd() = 0;
    virtual void Close() = 0;
    virtual ssize_t Recv(std::string* out) = 0;
    virtual ssize_t Send(const std::string& in) = 0;
    virtual bool Connect(InetAddr& peer) = 0;
public:
    // tcp 创建监听套接字：socket bind listen
    void BulidListenSocketMethod(int _port)
    {
        CreateSocketOrDie();
        BindSocketOrDie(_port);
        ListenSocketOrDie(gbacklog);
    }
    void BuildClientSocketMethod()
    {
        CreateSocketOrDie();
    }
};

// 封装tcp 通信流程 -> 创建监听套接字：socket bind listen , 接收连接：accept
class TcpSocket : public Socket
{
public:
    TcpSocket(int sockfd = gsockfd) : _sockfd(sockfd)
    {
    }

    // 实现基类中的方法
    void CreateSocketOrDie() override
    {
        _sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_sockfd < 0)
        {
            LOG(LogLevel::FATAL) << "创建套接字失败";
            exit(SOCKET_CREATE_ERR);
        }
        LOG(LogLevel::INFO) << "创建套接字成功";
    }

    void BindSocketOrDie(int port) override
    {
        // 需要网络地址
        InetAddr local(port);
        // bind 成功返回0，失败返回-1
        int n = bind(_sockfd, local.Addr(), local.Length());
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "绑定套接字失败";
            exit(SOCKET_BIND_ERR);
        }
        LOG(LogLevel::INFO) << "绑定套接字成功";
    }

    void ListenSocketOrDie(int backlog) override
    {
        int n = listen(_sockfd, backlog);
        // listen 返回: 成功返回0，失败返回-1
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "监听套接字失败";
            exit(SOCKET_BIND_ERR);
        }
        LOG(LogLevel::INFO) << "监听套接字成功";
    }

    std::shared_ptr<Socket> Accept(InetAddr *clientaddr) override
    // 输出型参数，返回文件描述符-> 文件描述符也使用TcpSocket 封装，智能指针shared_ptr,基类指针，指向子类
    {
        // accept 在接收连接的同时需要知道是谁发过来的，并且成功之后会返回文件描述符
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);

        int sockfd = accept(_sockfd, (struct sockaddr *)&peer, &len);
        if (sockfd < 0)
        {
            LOG(LogLevel::FATAL) << "获取连接失败";
            // 服务器不会因为获取连接失败而退出
            return nullptr;
        }
         LOG(LogLevel::INFO) << "获取连接失败";
        //将客户的网络地址信息传出去
        clientaddr->Init(peer);
         return std::make_shared<TcpSocket>(sockfd);
    }

    int Sockfd() override
    {
        return _sockfd;
    }

    void Close() override
    {
        close(_sockfd);
    }

    ssize_t Recv(std::string* out) override
    {
        char buffer[1024];
        buffer[0]=0;
        //从文件描述符中读取数据，通过参数传递过去；返回值为读取到的数据个数，交给上层去判断处理
        //使用recv 从文件中读取数据
        ssize_t n = recv(_sockfd , buffer , sizeof(buffer)-1 , 0);
        if(n > 0)
        {
            buffer[n]=0;//手动添加\0
            //追加到输出型参数的后面
            *out += buffer;
        } 
        return n;
    }
    ssize_t Send(const std::string& in) override //输入型参数 
    {
        //send 的返回值为将数据拷贝到发送缓冲区的个数
        return send(_sockfd , in.c_str() , in.size() , 0);
    }

    bool Connect(InetAddr& peer) override
    {
        //connect 成功返回0，失败返回-1
        int n = connect(_sockfd , peer.Addr() , peer.Length());
        if(n == 0) return true;
        else return false;
    }
    ~TcpSocket()
    {}

private:
    int _sockfd;
};

#endif